<?php
define('PDO_SQL_QUERY_FETCH_ROW', 1);
define('PDO_SQL_QUERY_FETCH_ALL', 2);
define('PDO_SQL_QUERY_FETCH_COLUMN', 3);
define('PDO_SQL_EXECUTE_INSERT', 4);
define('PDO_SQL_EXECUTE_UPDATE_OR_DELETE', 5);

/**
 * PDO Extender for MySQL。
 * 
 * @author Lee Lee
 * @version 1.0
 *
 */
class LeeNT_MySql
{
	
	/**
	 * 数据库 PDO 操作对象。
	 * 
	 * @var PDO
	 */
	private $_db = NULL;
	
	/**
	 * 单一(缺省)服务器连接参数。
	 * 
	 * @var array
	 */
	private $_db_params = NULL;
	
	/**
	 * 若干服务器参数配置。
	 *
	 * @var array
	 */
	private $_db_multi_server = array();
	
	/**
	 * 连接活动状态。
	 * 
	 * @var boolean
	 */
	private $_actived = false;
	
	/**
	 * 开启/关闭 DEBUG 模式。
	 * 
	 * @var boolean
	 */
	private $_debug = false;
	
	/**
	 * 构造函数。
	 * 
	 * @param array $db_params 数据库连接参数。
	 * @return LeeNT_MySql
	 */
	public function __construct($db_params = NULL)
	{
		$this->_db_params = $db_params;
	}
	
	/**
	 * 打开数据库连接。
	 */
	public function connect()
	{
		$this->_db = new PDO('mysql:dbname=' . $this->_db_params['dbname'] . ';host=' . $this->_db_params['host'], $this->_db_params['user'], $this->_db_params['pass']);
		$this->_db->exec("SET NAMES `" . $this->_db_params['charset'] . "`");
		$this->_actived = true;
	}
	
	/**
	 * 关闭数据库连接。
	 */
	public function close()
	{
		$this->_actived = false;
		$this->_db_params = NULL;
		$this->_db = null;
	}
	
	/**
	 * 打开指定的数据库连接。
	 *
	 * @param int $key
	 */
	public function change($key)
	{
		if ($this->_actived) {
			$this->close();
		}
		$this->_db_params = $this->_db_multi_server[$key];
	}
	
	/**
	 * 设置缺省连接。
	 *
	 * @param array $dbParam
	 */
	public function setDefaultDatabase($dbParam)
	{
		if (is_null($this->_db_params)) {
			$this->_db_params = $dbParam;
		}
	}
	
	/**
	 * 注册多个数据库连接参数数组。
	 *
	 * @param int $dbIndex
	 * @param array $dbParams
	 */
	public function register($dbIndex, $dbParams)
	{
		$this->_db_multi_server[$dbIndex] = $dbParams;
	}
	
	/**
	 * 获取或设置 DEBUG 模式。
	 * 
	 * @param boolean $debug
	 */
	public function debug($debug = false)
	{
		if ($debug !== false)
			$this->_debug = true;
	}
	
	/**
	 * 切换当前连接使用的字符集。
	 * 
	 * @param string $charset
	 */
	public function setEncoding($charset = 'utf8')
	{
		if ($this->_actived)
			$this->_db->exec("SET NAMES `" . $charset . "`");
	}
	
	/**
	 * 切换当前使用的数据库。
	 * 
	 * @param string $database
	 */
	public function changeEncoding($database)
	{
		if ($this->_actived)
			$this->_db->exec("USE `" . $database . "`");
	}
	
	/**
	 * 执行 SELECT 查询，并返回一行记录。
	 * 
	 * @param string $sql
	 * @param array $data
	 * @param integer $fetch_style
	 * @param string $field_name_as_key
	 * @return array
	 */
	public function fetch($sql, $data = NULL, $fetch_style = PDO::FETCH_ASSOC, $field_name_as_key = NULL)
	{
		$d = $this->Execute($sql, $data, PDO_SQL_QUERY_FETCH_ROW, $fetch_style);
		
		if (!is_null($field_name_as_key) && $fetch_style == PDO::FETCH_ASSOC)
			$d = $this->ConventFieldAsResultKey($d, $field_name_as_key);
		
		return $d;
	}
	
	/**
	 * 执行 SELECT 查询，并返回全部记录。
	 * 
	 * @param string $sql
	 * @param array $data
	 * @param integer $fetch_style
	 * @param string $field_name_as_key
	 * @param string $from_charset
	 * @param string $to_charset
	 * @return array
	 */
	public function fetchAll($sql, $data = NULL, $fetch_style = PDO::FETCH_ASSOC, $field_name_as_key = NULL, $from_charset = NULL, $to_charset = 'UTF-8')
	{
		$d = $this->Execute($sql, $data, PDO_SQL_QUERY_FETCH_ALL, $fetch_style);
		
		if (!is_null($field_name_as_key) && $fetch_style == PDO::FETCH_ASSOC)
			$d = $this->ConventFieldAsResultKey($d, $field_name_as_key);
		
		if (!is_null($from_charset)) {
			$this->GetEncoding($d, $from_charset, $to_charset);
		}
		
		return $d;
	}
	
	/**
	 * 返回单行结果查询中指定列的值。
	 * 
	 * @param string $sql
	 * @param array $data
	 * @param integer $column_index
	 * @return mixed
	 */
	public function scalar($sql, $data = NULL, $column_index = 0)
	{
		$d = $this->Execute($sql, $data, PDO_SQL_QUERY_FETCH_COLUMN, 0, $column_index);
		return $d;
	}
	
	/**
	 * 执行 INSERT 查询，并返回最后插入的主键标识。
	 * 
	 * @param string $sql
	 * @param array $data
	 * @return integer
	 */
	public function insert($sql, $data = NULL)
	{
		$d = $this->Execute($sql, $data, PDO_SQL_EXECUTE_INSERT);
		return $d;
	}
	
	/**
	 * 执行 UPDATE 查询，并返回影响的行数。
	 * 
	 * @param string $sql
	 * @param array $data
	 * @return integer
	 */
	public function update($sql, $data = NULL)
	{
		$d = $this->Execute($sql, $data, PDO_SQL_EXECUTE_UPDATE_OR_DELETE);
		return $d;
	}
	
	/**
	 * 执行 DELETE 查询，并返回影响的行数。
	 * 
	 * @param string $sql
	 * @param array $data
	 * @return integer
	 */
	public function delete($sql, $data = NULL)
	{
		$d = $this->Execute($sql, $data, PDO_SQL_EXECUTE_UPDATE_OR_DELETE);
		return $d;
	}
	
	/**
	 * 执行 INSERT 查询 —— 简易插入模式。
	 *
	 * @param string $tblname
	 * @param array $fields
	 * @return integer
	 */
	public function simple_insert($tblname, $fields)
	{
		$d = array();
		$s = "INSERT INTO `" . $tblname . "` SET ";
		$i = 0;
		foreach ($fields as $key => $value)
		{
			if ($i > 0)
				$s .= ", ";
			
			$s .= "`" . $key . "` = ?";
			$d[] = $value;
			$i++;
		}
		
		return $this->insert($s, $d);
	}
	
	/**
	 * 执行 UPDATE 查询 —— 简易更新模式。
	 *
	 * @param string $tblname
	 * @param array $fields
	 * @param string $where
	 * @param array $where_data
	 * @return integer
	 */
	public function simple_update($tblname, $fields, $where = NULL, $where_data = NULL)
	{
		$d = array();
		$s = "UPDATE `" . $tblname . "` SET ";
		$i = 0;
		foreach ($fields as $key => $value)
		{
			if ($i > 0)
				$s .= ", ";
			
			$s .= "`" . $key . "` = ?";
			$d[] = $value;
			$i++;
		}
		if (!is_null($where))
		{
			$s .= " WHERE 1 " . $where;
			
			if (is_array($where_data))
			{
				foreach ($where_data as $key => $value)
				{
					$d[] = $value;
				}
			}
		}
		
		return $this->update($s, $d);
	}
	
	/**
	 * 处理 SQL 语句中的特殊字符。
	 *
	 * @param string $str
	 * @return string
	 */
	public function ProcessSql($str)
	{
		$str = preg_replace('/[\\\]+\\\'/i', '\'', $str);
		$str = preg_replace('/\r\n/i', '\\\r\\\n', $str);
		$str = str_replace('\'', '\\\'', $str);
		
		return $str;
	}
	
	/**
	 * 执行查询并建立 SQL 语句(REPLACE)。
	 *
	 * @param string $tblname
	 * @param string $sql
	 * @param array $data
	 * @return string
	 */
	public function BuildSQLReplace($tblname, $sql, $data = NULL)
	{
		$sResult = '';
		
		$columns = $this->FetchAll("SHOW COLUMNS FROM `" . $tblname . "`", NULL, PDO::FETCH_ASSOC, 'Field');
		$column_names = array_keys($columns);
		
		$d = $this->FetchAll($sql, $data, PDO::FETCH_ASSOC, NULL);
		if ($d)
		{
			foreach ($d as $v)
			{
				$val = array();
				foreach ($columns as $k => $vs)
				{
					if (false !== strpos($vs['Type'], 'varchar(') || false !== strpos($vs['Type'], 'text') || false !== strpos($vs['Type'], 'char(') || false !== strpos($vs['Type'], 'datetime'))
					{
						$val[] = "'" . $this->ProcessSql($v[$k]) . "'";
					}
					else
					{
						$val[] = $v[$k];
					}
				}
				
				$sResult .= "REPLACE INTO `" . $tblname . "`(`" . implode("`, `", $column_names) . "`) VALUES(" . implode(", ", $val) . ");\r\n";
			}
		}
		unset($columns, $column_names, $d);
		
		return $sResult;
	}
	
	/**
	 * Execute SQL text.
	 *
	 * @param string $sql
	 */
	public function Exec($sql)
	{
		$this->_db->exec($sql);
	}
	
	/**
	 * 执行 SQL 查询。
	 * 
	 * @param string $sql	需执行的 SQL 语句(支持预编译语法)。
	 * @param array $data	传入的参数数组。
	 * @param integer $exec_type	处理类型(INSERT/UPDATE/DELETE/SELECT)。
	 * @param integer $fetch_style	指定结果数组输出的结构形式。
	 * @param integer $column_index	执行单行X列查询时，指定返回的列索引值。
	 * @access private
	 * @return mixed	当返回 FALSE 时，表明查询操作失败。
	 */
	private function Execute($sql, $data = NULL, $exec_type = PDO_SQL_QUERY_FETCH_ALL, $fetch_style = PDO::FETCH_ASSOC, $column_index = 0)
	{
		if (false == $this->_actived)
		{
			$this->connect();
		}
		
		$d = false;
		
		$sth = $this->_db->prepare($sql);
		
		if (false == (is_null($data) || (is_array($data) && count($data) == 0)))
		{
			foreach ($data as $key => $value)
			{
				if (is_array($value))
					$sth->bindValue(1 + $key, $value[0], $value[1]);
				else
					$sth->bindValue(1 + $key, $value);
			}
		}
		
		try
		{
			$sth->execute();
		}
		catch ( Exception $ex )
		{
			$this->_db = NULL;
			trigger_error('Query Error! ' . $ex->getMessage(), E_USER_ERROR);
			return false;
		}
		
		switch ($exec_type)
		{
			case PDO_SQL_EXECUTE_INSERT :
				$d = $this->_db->lastInsertId();
				break;
			case PDO_SQL_EXECUTE_UPDATE_OR_DELETE :
				$d = $sth->rowCount();
				break;
			case PDO_SQL_QUERY_FETCH_ROW :
				$d = $sth->fetch($fetch_style);
				break;
			case PDO_SQL_QUERY_FETCH_ALL :
				$d = $sth->fetchAll($fetch_style);
				break;
			case PDO_SQL_QUERY_FETCH_COLUMN :
				$d = $sth->fetchColumn($column_index);
				break;
		}
		
		$sth->closeCursor();
		$sth = null;
		
		return $d;
	}
	
	/**
	 * 获取分页信息数组。
	 *
	 * @param integer $count
	 * @param integer $size
	 * @param integer $current
	 * @param integer $style
	 * @return array ('html' => 分页 HTML 代码, 'size' => 每页显示 X 条记录, 'pi' => 当前页码, 'pt' => 总页数, 'ps' => 记录起始索引值)
	 */
	function getPages($count, $size = 10, $offset = 'offset', $style = 1)
	{
		$iPageCount = ceil($count / $size);
		$iPageIndex = ( int ) $_GET[$offset];
		if ($iPageIndex <= 0)
			$iPageIndex = 1;
		
		if ($iPageCount <= 0)
			$iPageCount = 1;
		
		$str = '';
		
		if (1 == $style)
		{
			if ($iPageIndex > 1)
			{
				$str .= '<a href="' . $this->_bp($offset, 1) . '">First</a>';
				$str .= '<a href="' . $this->_bp($offset, $iPageIndex - 1) . '">Prev</a>';
			}
			if ($iPageIndex < $iPageCount)
			{
				$str .= '<a href="' . $this->_bp($offset, $iPageIndex + 1) . '">Next</a>';
				$str .= '<a href="' . $this->_bp($offset, $iPageCount) . '">Last</a>';
			}
		}
		if (2 == $style)
		{
			$PageStep = 8;
			
			if ($iPageIndex > 1)
			{
				$str .= '<a href="' . $this->_bp($offset, 1) . '">First</a>';
				$str .= '<a href="' . $this->_bp($offset, $iPageIndex - 1) . '">Prev</a>';
			}
			
			$b = $iPageIndex - 2;
			if ($b <= 0)
				$b = 1;
			$c = $b + $PageStep - 1;
			if ($c > $iPageCount)
				$c = $iPageCount;
			for ($i = $b; $i <= $c; $i++)
				$str .= "<a href=\"" . $this->_bp($offset, $i) . "\" class=\"" . ($iPageIndex == $i ? "current" : "num") . "\">" . $i . "</a>";
			
			if ($iPageIndex < $iPageCount)
			{
				$str .= '<a href="' . $this->_bp($offset, $iPageIndex + 1) . '">Next</a>';
				$str .= '<a href="' . $this->_bp($offset, $iPageCount) . '">Last</a>';
			}
		}
		
		$d = array(
			'Code'        => $str, 
			'PageSize'    => $size, 
			'RecordCount' => $count, 
			'PageIndex'   => $iPageIndex, 
			'PageCount'   => $iPageCount, 
			'StartIndex'  => ($iPageIndex - 1) * $size
		);
		$d['begin'] = $d['ps'] + 1;
		$d['end'] = $d['begin'] + $size - 1;
		
		return $d;
	}
	
	/**
	 * 转换字符编码。
	 *
	 * @param array $data
	 * @param string $from_charset
	 * @param string $to_charset
	 */
	private function GetEncoding(&$data, $from_charset = 'GBK', $to_charset = 'UTF-8')
	{
		if (is_array($data) && function_exists('iconv')) {
			foreach ($data as $k => $v) {
				if (is_string($v)) {
					$data[$k] = iconv($from_charset, $to_charset, $v);
				} elseif (is_array($v)) {
					$this->GetEncoding($data[$k], $from_charset, $to_charset);
				}
			}
		}
	}
	
	/**
	 * 将数组中指定列的值作为数组键名称。
	 * 
	 * @param array &$data
	 * @param string $field_name
	 * @return array
	 */
	private function ConventFieldAsResultKey(&$data, $field_name)
	{
		if (is_array($data))
		{
			$d = array();
			foreach ($data as $value)
				$d[$value[$field_name]] = $value;
			
			$data = NULL;
			
			return $d;
		}
		
		return false;
	}
	
	/**
	 * 返回分页链接代码。
	 *
	 * @param integer $offset
	 * @param integer $index
	 * @return string
	 */
	private function _bp($offset, $index)
	{
		if (empty($_SERVER['QUERY_STRING']))
		{
			return $_SERVER['SCRIPT_NAME'] . '?' . $offset . '=' . $index;
		}
		else
		{
			if (preg_match('/(\?|&)' . $offset . '=[0-9]+/i', $_SERVER['REQUEST_URI']))
			{
				return preg_replace('/(\?|&)' . $offset . '=[0-9]+/is', '\\1' . $offset . '=' . $index, $_SERVER['REQUEST_URI']);
			}
			else
			{
				return $_SERVER['REQUEST_URI'] . '&' . $offset . '=' . $index;
			}
		}
	}
}